fn add(left: u64, right: u64) -> u64 {
    left + right
}

use proc_macro::{Ident, TokenStream};
use quote::{quote, ToTokens};
use syn::{parse, parse_macro_input, Data, DeriveInput, Expr, ItemFn, Attribute, Meta};
use proc_macro_error::{Diagnostic, Level};

#[proc_macro]
pub fn print_expr(input: TokenStream) -> TokenStream {
    let expr = parse_macro_input!(input as Expr);
    println!("expand proc macro to generate print_expr code"); // client-code compile-time print
    let expanded = quote! {
        {
            let result = #expr;
            println!("{} = {:?}", stringify!(#expr), result);
            result
        }
    };
    TokenStream::from(expanded)
}

#[proc_macro_derive(ToString)]
pub fn derive_to_string(input: TokenStream) -> TokenStream {
    let derive_input = parse_macro_input!(input as DeriveInput);
    println!("expand derive macro to generate ToString code");

    let name = derive_input.ident;
    let data = match derive_input.data {
        Data::Enum(data) => data,
        _ => {
            let diagnostic = Diagnostic::new(Level::Error, "自定义错误信息".to_owned());
            diagnostic.emit();
            panic!("ToString can only be derived for enums");
        },
    };
    let variants = data.variants.iter().map(|variant|{
        let variant_name = &variant.ident;
        quote! {
            #name::#variant_name => String::from(stringify!(#variant_name)),
        }
    });

    let expanded = quote! {
        impl #name {
            pub fn to_string(&self) -> String {
                match self {
                    #(#variants)*
                }
            }
        }
    };
    TokenStream::from(expanded)
}

#[proc_macro_attribute]
pub fn log_args(attrs: TokenStream, item: TokenStream) -> TokenStream {

    println!("attrs : {:?}", attrs);
    let attrs_meta = parse_macro_input!(attrs as Meta);

    let mut function = parse_macro_input!(item as ItemFn);
    let fsig = &function.sig;
    let fname = &fsig.ident;

    let arg_idents = fsig.inputs.iter().map(|arg| {
        let arg_tok = quote! {#arg};
        let arg_str = arg_tok.to_string();
        let arg_name = arg_str.split_whitespace().next().expect("arg don't have a name");
        syn::Ident::new(arg_name, proc_macro2::Span::call_site())
    }).collect::<Vec<_>>();

    let stats = function.block.stmts.iter().map(|stmt| {
        quote! {#stmt}
    }).collect::<Vec<_>>();

    let fmt_info = format!(
        "{} was called with args: {}", quote!(#fname),
        arg_idents.iter().map(|_| "{}").collect::<Vec<_>>().join(", ")
    );

    let new_body = quote! {
        {
            println!(
                #fmt_info, 
                #(
                    {format!("{} = {:?}", stringify!(#arg_idents), &#arg_idents)}
                ),*
            );
            #(#stats)*
        }
    };

    function.block = syn::parse2(new_body).expect("Failed to parse new_body");
    TokenStream::from(quote! { #function })
}